package jmine.tec.script.persistent.repository;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jmine.tec.script.ScriptExecutor;
import jmine.tec.script.impl.ConcurrentScriptDispatcher;
import jmine.tec.script.persistent.ScriptController;
import jmine.tec.script.persistent.context.impl.ScriptExecutionContextFactoryImpl;
import jmine.tec.script.persistent.dao.ScriptLanguageIdentifier;
import jmine.tec.script.persistent.domain.Script;
import jmine.tec.script.persistent.domain.ScriptLanguage;
import jmine.tec.script.persistent.domain.ScriptPurpose;
import bancosys.tec.utils.db.descriptor.FlatReferenceDatabaseBuilder;
import bancosys.tec.utils.db.descriptor.FlatReferenceDatabaseBuilder.ReferenceDatabaseDescriptionType;
import bancosys.tec.utils.db.descriptor.ReferenceDatabaseDescription;
import bancosys.tec.utils.db.descriptor.ReferenceDatabaseEntry;
import bancosys.tec.utils.db.descriptor.ReferenceDatabaseException;
import bancosys.tec.utils.db.descriptor.ReferenceDatabaseParser;

/**
 * Repositório de scripts transiente.
 * 
 * @author lundberg
 */
public class TransientScriptRepository {

    private static final Pattern SCRIPT_MNE_PAT = Pattern.compile("(?:.*/)(\\w+)\\.\\w+");

    private Map<String, ReferenceDatabaseEntry> entries = new HashMap<String, ReferenceDatabaseEntry>();

    private Map<String, ScriptLoader> loaders = new HashMap<String, ScriptLoader>();

    private Map<String, Script> scripts = new HashMap<String, Script>();

    private Map<String, ScriptPurpose> purposes = new HashMap<String, ScriptPurpose>();

    private Map<Long, ScriptLanguage> languages = new HashMap<Long, ScriptLanguage>();

    private ScriptController controller = new ScriptController();

    private MockBeanFactory beanFactory = new MockBeanFactory();

    private List<ScriptExecutor> executors = new ArrayList<ScriptExecutor>();

    public TransientScriptRepository() {
        ScriptExecutionContextFactoryImpl contextFactory = new ScriptExecutionContextFactoryImpl();
        contextFactory.setController(this.controller);
        this.controller.setBeanFactory(this.beanFactory);
        this.controller.setContextFactory(contextFactory);
        this.controller.setDispatcher(new ConcurrentScriptDispatcher());
        this.addPurpose(ScriptPurposeBuilder.init().build());
        this.addPurpose(ScriptPurposeBuilder.api().build());
    }

    public Set<Script> loadAll() {
        Set<Script> scripts = new HashSet<Script>();
        scripts.addAll(this.scripts.values());
        for (String mnemonico : this.entries.keySet()) {
            scripts.add(this.loadScript(mnemonico));
        }
        return scripts;
    }

    public Script loadScript(String mnemonico, ScriptLanguage language) {
        String mnemonicoReal = String.format("%s_%s", mnemonico, language.getAbreviacao());
        if (this.scripts.containsKey(mnemonicoReal)) {
            return this.scripts.get(mnemonicoReal);
        }
        ReferenceDatabaseEntry entry = this.findEntry(mnemonico);
        return this.loadScript(entry.getType(), entry.getClasspathResourcePath());
    }

    public Script loadScript(String mnemonico) {
        if (this.scripts.containsKey(mnemonico)) {
            return this.scripts.get(mnemonico);
        }
        ReferenceDatabaseEntry entry = this.findEntry(mnemonico);
        return this.loadScript(entry.getType(), entry.getClasspathResourcePath());
    }

    public Script loadScript(String type, String path) {
        ScriptLoader loader = this.findLoader(type);
        Script script = loader.load(this, path);
        script.setController(this.controller);
        this.scripts.put(script.getMnemonico(), script);
        return script;
    }

    public void addRefDB(String... resources) {
        List<ReferenceDatabaseDescription> descriptions;
        try {
            descriptions = new ReferenceDatabaseParser().getDescriptions(resources);
        } catch (ReferenceDatabaseException e) {
            throw new RuntimeException("Erro inesperado efetuando parse da descrição de base de referência.", e);
        }
        List<ReferenceDatabaseEntry> entries = new LinkedList<ReferenceDatabaseEntry>();
        entries.addAll(FlatReferenceDatabaseBuilder.flatten(descriptions, ReferenceDatabaseDescriptionType.POSTSCRIPT));
        entries.addAll(FlatReferenceDatabaseBuilder.flatten(descriptions, ReferenceDatabaseDescriptionType.REFERENCE));
        for (ReferenceDatabaseEntry entry : entries) {
            if (this.loaders.containsKey(entry.getType())) {
                Matcher mnemonicoMatcher = SCRIPT_MNE_PAT.matcher(entry.getClasspathResourcePath());
                if (mnemonicoMatcher.matches()) {
                    this.entries.put(mnemonicoMatcher.group(1), entry);
                } else {
                    throw new IllegalStateException("Nome inesperado para script: " + entry.getClasspathResourcePath());
                }
            }
        }
    }

    /**
     * @return the purposes
     */
    public Map<String, ScriptPurpose> getPurposes() {
        return Collections.unmodifiableMap(this.purposes);
    }

    /**
     * Adiciona suporte a um propósito de script
     * 
     * @param purpose purpose
     */
    public void addPurpose(ScriptPurpose purpose) {
        this.purposes.put(purpose.getMnemonico(), purpose);
    }

    public void addLoader(ScriptLoader loader) {
        this.loaders.put(loader.type(), loader);
    }

    public void addLanguage(ScriptLanguage language) {
        this.languages.put(language.getId(), language);
    }

    public void addExecutor(ScriptExecutor executor) {
        this.executors.add(executor);
        ConcurrentScriptDispatcher dispatcher = new ConcurrentScriptDispatcher();
        dispatcher.setScriptExecutors(this.executors.toArray(new ScriptExecutor[0]));
        this.controller.setDispatcher(dispatcher);
    }

    public void addToBeanFactory(String name, Object bean) {
        this.beanFactory.addBean(name, bean);
    }

    public void cleanBeanFactory() {
        this.beanFactory.clean();
    }

    private ScriptLoader findLoader(String type) {
        if (!this.loaders.containsKey(type)) {
            throw new UnsupportedOperationException("Loader não encontrado para script de tipo " + type);
        }
        return this.loaders.get(type);
    }

    private ReferenceDatabaseEntry findEntry(String mnemonico) {
        if (!this.entries.containsKey(mnemonico)) {
            for(ScriptLanguage lang : this.languages.values()) {
                String api = mnemonico + "_" + lang.getAbreviacao();
                if (entries.containsKey(api)) {
                    return entries.get(api);
                }
            }
            throw new IllegalArgumentException("Script com mnemônico não encontrado no repositório: " + mnemonico);
        }
        return this.entries.get(mnemonico);
    }

    public ScriptPurpose findPurpose(String mnemonico) {
        if (!this.purposes.containsKey(mnemonico)) {
            throw new IllegalArgumentException("Finalidade com mnemônico não encontrado no repositório: " + mnemonico);
        }
        return this.purposes.get(mnemonico);
    }

    public ScriptLanguage findLanguage(ScriptLanguageIdentifier identifier) {
        if (!this.languages.containsKey(identifier.getSystemId())) {
            throw new IllegalArgumentException("Linguagem com identificador não encontrado no repositório: " + identifier);
        }
        return this.languages.get(identifier.getSystemId());
    }
}
